﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NLog;
using Castle.Windsor;
using NGinnBPM;
using NGinnBPM.MessageBus;
using NGinnBPM.MessageBus.Impl;
using NGinnBPM.Lib;
using NGinnBPM.Lib.Interfaces;
using NGinnBPM.Lib.Util;
using NGinnBPM.Services;
using NGinnBPM.Services.Worklist;
using NGinnBPM.Runtime;
using NGinnBPM.Lib.Messages;
using NGinnBPM.Lib.Interfaces.Worklist;
using NGinnBPM.Runtime.Messages;
using NGinnBPM.Runtime.Events;
using Castle.MicroKernel.Registration;
using System.IO;
using NGinnBPM.MessageBus.Windsor;
using System.Reflection;
using NGO = NGinnBPM.Lib.Operations;


namespace NGinnBPM.HostConfig
{
    /// <summary>
    /// NGinn fluent configurator
    /// based on Castle Windsor container
    /// </summary>
    public partial class NGinnHostConfigurator
    {
        protected IWindsorContainer _wc;
        protected Logger log = LogManager.GetCurrentClassLogger();
        private IConfigParameterProvider _cfg = new DictionaryConfigProvider();
        
        private NGinnHost _host = null;
        private string _basedir;
        private MessageBusConfigurator _mbConfig;
        

        protected NGinnHostConfigurator()
        {
            _wc = new WindsorContainer();
            RegisterBaseServices();
        }

        protected NGinnHostConfigurator(IWindsorContainer wc)
        {
            _wc = wc;
            if (_wc.GetService<IConfigParameterProvider>() != null)
            {
                _cfg = _wc.Resolve<IConfigParameterProvider>();
            }
            RegisterBaseServices();
        }

        private void RegisterBaseServices()
        {
            if (_wc.GetService<IServiceResolver>() == null)
            {
                _wc.Register(Component.For<IServiceResolver>().ImplementedBy<NGinnBPM.MessageBus.Windsor.WindsorServiceResolver>().LifeStyle.Singleton);
            }
        }

        public static NGinnHostConfigurator BeginConfig()
        {
            return new NGinnHostConfigurator();
        }

        public static NGinnHostConfigurator BeginConfig(IWindsorContainer container)
        {
            return new NGinnHostConfigurator(container);
        }

        public NGinnHostConfigurator SetConfigProvider(IConfigParameterProvider cfg)
        {
            _cfg = cfg;
            return this;
        }

        public IConfigParameterProvider Config
        {
            get { return _cfg; }
        }

        public IServiceResolver ServiceResolver
        {
            get { return _wc.Resolve<IServiceResolver>(); }
        }

        public NGinnHostConfigurator SetConfigFile(string cfgFileName)
        {
            NGinnBPM.Lib.Util.DefaultConfigProvider cfg = new NGinnBPM.Lib.Util.DefaultConfigProvider();
            cfg.ConfigFile = cfgFileName;
            return SetConfigProvider(cfg);
        }

        public NGinnHostConfigurator SetConfigOptions(System.Collections.IDictionary options)
        {
            NGinnBPM.Lib.Util.DictionaryConfigProvider cfg = new NGinnBPM.Lib.Util.DictionaryConfigProvider(options);
            return SetConfigProvider(cfg);
        }

        public NGinnHostConfigurator CustomizeContainer(Action<IWindsorContainer> act)
        {
            act(_wc);
            return this;
        }

        /// <summary>
        /// By default it's the directory of configuration file
        /// </summary>
        /// <param name="dir"></param>
        /// <returns></returns>
        public NGinnHostConfigurator SetBaseDirectory(string dir)
        {
            _cfg.Set("configdir", dir);
            return this;
        }



        

        /// <summary>
        /// Config properties
        /// NGinnBPM.MessageBus.WebServer.Endpoint - endpoint for nginn web server
        /// you have to configure nginn messagebus web server to get nginn http interface
        /// </summary>
        /// <returns></returns>
        private NGinnHostConfigurator ConfigureNGinnMessageBus()
        {
            if (_wc.GetService<IMessageBus>() != null) throw new Exception("Already configured");
            
            CustomizeContainer(delegate(IWindsorContainer wc)
            {
                _mbConfig =
                    MessageBusConfigurator.Begin(wc)
                        .SetEndpoint(NGinnMessageBusEndpoint)
                        .SetConnectionStrings(GetNGinnMessageBusConnectionStrings())
                        .AutoStartMessageBus(false)
                        .SetMaxConcurrentMessages(Config.GetInt("NGinn.MessageBus.MaxConcurrentMessages", 2))
                        .SetMessageRetentionPeriod(TimeSpan.FromHours(Config.GetInt("NGinn.MessageBus.MessageRetentionPeriodHours", 12)))
                        .ReuseReceiveConnectionForSending(Config.GetBool("NGinn.MessageBus.ReuseReceiveConnectionForSending", true))
                        .UseApplicationManagedConnectionForSending(Config.GetBool("NGinn.MessageBus.UseApplicationManagedConnectionForSending", true))
                        .ExposeReceiveConnectionToApplication(Config.GetBool("NGinn.MessageBus.ExposeReceiveConnectionToApplication", true))
                        .UseTransactionScope(true);
                if (_cfg.GetBool("NGinn.MessageBus.UseSqlSubscriptions", true))
                    _mbConfig.UseSqlSubscriptions();
                if (!string.IsNullOrEmpty(_cfg.GetString("NGinn.MessageBus.StaticRoutingConfigFile", null)))
                {
                    _mbConfig.UseStaticMessageRouting(_cfg.GetString("NGinn.MessageBus.StaticRoutingConfigFile", null));
                }

                string addr = _cfg.GetString("NGinnBPM.MessageBus.WebServer.Endpoint", "");
                if (addr != null && addr.Length > 0)
                {
                    _mbConfig.ConfigureHttpReceiver(addr);
                    //add nginn bpm servlets
                    
                }
                _mbConfig.FinishConfiguration();
            });
            return this;
        }

        private void RegisterNGinnHttpServices()
        {
            MessageBusConfigurator.Begin(this._wc)
                .RegisterHttpHandlersFromAssembly(typeof(NGinnHostConfigurator).Assembly)
                .RegisterHttpHandlersFromAssembly(typeof(NGEnvironment).Assembly)
                .RegisterHttpMessageServicesFromAssembly(typeof(NGEnvironment).Assembly);
        }

        public NGinnHostConfigurator AddMessageBusConnectionString(string alias, string connStr)
        {
            _cfg.Set("NGinnBPM.MessageBus.ConnectionString." + alias, connStr);
            return this;
        }


        /// <summary>
        /// Set NGinn database connection string
        /// Or specify NGinn.ConnectionString config property
        /// </summary>
        /// <param name="connStr"></param>
        /// <returns></returns>
        public NGinnHostConfigurator SetNGinnDBConnectionString(string connStr)
        {
            NGinnDBConnectionString = connStr;
            return this;
        }

        /// <summary>
        /// Or specify NGinn.MessageQueue config property
        /// </summary>
        /// <param name="endpoint"></param>
        /// <returns></returns>
        public NGinnHostConfigurator SetNGinnMessageBusEndpoint(string endpoint)
        {
            NGinnMessageBusEndpoint = endpoint;
            return this;
        }



        

        



        /// <summary>
        /// Call this method after all other configuration options
        /// have been set.
        /// </summary>
        /// <returns></returns>
        public NGinnHostConfigurator FinishConfiguration()
        {
            
            if (_wc.GetService<IConfigParameterProvider>() == null)
            {
                _wc.Register(Component.For<IConfigParameterProvider>().Instance(_cfg));
            }
            if (_wc.GetService<IMessageBus>() == null)
            {
                ConfigureNGinnMessageBus();
            }
            
            if (_wc.GetService<IDefaultTaskInstanceFactory>() == null)
            {
                _wc.Register(Component.For<IDefaultTaskInstanceFactory>()
                    .ImplementedBy<NGinnBPM.Services.TaskInstanceFactory>()
                    .LifeStyle.Singleton
                    .Named("TaskInstanceFactory"));
            }
            if (_cfg.GetString("NGinnBPM.TaskInstanceSerializer", "TaskInstanceJsonSerializer") == "TaskInstanceJsonSerializer")
            {
                log.Info("Task instance serializer: TaskInstanceJsonSerializer");
                _wc.Register(Component
                    .For<ITaskInstanceSerializer>()
                    .ImplementedBy<NGinnBPM.Services.Persistence.TaskInstanceJsonSerializer>()
                    .Parameters(Parameter.ForKey("TaskInstanceFactory").Eq("${TaskInstanceFactory}"))
                    .LifeStyle.Singleton);

            }
            else
            {
                log.Info("Task instance serializer: SimpleTaskSerializer");
                _wc.Register(Component
                    .For<ITaskInstanceSerializer>()
                    .ImplementedBy<NGinnBPM.Services.Persistence.SimpleTaskSerializer>()
                    .LifeStyle.Singleton);
            }
            
            if (_wc.GetService<IProcessScriptManager>() == null)
            {
                if (_cfg.GetString("NGinnBPM.ProcessScript", "boo") == "jint")
                {
                    //wc.Register(Reg.Component.For<IProcessInstanceLockManager>().ImplementedBy<FakeInstanceLockManager>().LifeStyle.Singleton);
                    _wc.Register(Component.For<IProcessScriptManager>().ImplementedBy<NGinnBPM.Services.Scripting.JintScript.JintProcessScriptManager>()
                        .LifeStyle.Singleton);
                }
                else
                {
                    _wc.Register(Component.For<IProcessScriptManager>()
                        .ImplementedBy<NGinnBPM.Services.Scripting.BooScript.BooProcessScriptManager>()
                        .Parameters(Parameter.ForKey("BaseDirectory").Eq(Path.Combine(BaseDirectory, "temp\\BooProcessScriptManager")))
                        .LifeStyle.Singleton);
                }
            }

            _wc.Register(Component.For<IPersistentCounters>()
                .ImplementedBy<NGinnBPM.Services.Utilities.PersistentCounters>()
                .LifeStyle.Singleton
                .Parameters(Parameter.ForKey("BaseDirectory").Eq(Path.Combine(BaseDirectory, "var\\PersistentCounters"))));

            _wc.Register(Component.For<IBarrierRegistry>()
                .ImplementedBy<NGinnBPM.Services.Utilities.BarrierRegistry>()
                .LifeStyle.Singleton
                .Parameters(Parameter.ForKey("BaseDirectory").Eq(Path.Combine(BaseDirectory, "var\\BarrierRegistry"))));

            if (_wc.GetService<IProcessPackageRepository>() == null)
            {
                _wc.Register(Component.For<IProcessPackageRepository>()
                    .ImplementedBy<FSProcessPackageRepository>()
                    .LifeStyle.Singleton
                    .Parameters(Parameter.ForKey("BaseDirectory").Eq(Path.Combine(BaseDirectory, "PackageRepository"))));
            }

            if (_wc.GetService<INGEnvironment>() == null)
            {
                _wc.Register(
                    Component.For<INGEnvironment>()
                        .Forward<IMessageConsumer<ITaskInstanceMessage>>()
                        .Forward<IMessageConsumer<StartProcessMessage>>()
                        .Forward<IMessageConsumer<EnableTaskMessage>>()
                        .Forward<IMessageConsumer<EnableMultiInstanceTaskMessage>>()
                        .Forward<IMessageConsumer<CancelTaskMessage>>()
                        .Forward<IMessageConsumer<FailTaskMessage>>()
                        .Forward<IMessageConsumer<WorkItemCompleted>>()
                    //IMessageConsumer of ProcessStartedEvent,\
                    //IMessageConsumer of ProcessCompletedEvent,\
                    //IMessageConsumer of ProcessStartedEvent,\
                        .ImplementedBy<NGinnBPM.Runtime.NGEnvironment>()
                        .Named("NGEnvironment")
                        .DependsOn(new {
                            ScheduleTasksWithMessageBus = _cfg.GetBool("NGinnBPM.ScheduleTasksWithMessageBus", false)
                        })
                        .LifeStyle.Singleton
                    );
            }

            if (_wc.GetService<INGEngine>() == null)
            {
                _wc.Register(Component.For<INGEngine>()
                    .ImplementedBy<NGEngine>()
                    .Forward<IMessageConsumer<TaskKickableMessage>>()
                    .Forward<IMessageConsumer<Heartbeat>>()
                    .Forward<NGinnBPM.MessageBus.Impl.IStartableService>()
                    //.Forward<IMessageConsumer<KickTaskEvent>>()
                    .DependsOn(new {
                        MaxConcurrentTasks = 10,
                        ScheduleTasksWithMessageBus = _cfg.GetBool("NGinnBPM.ScheduleTasksWithMessageBus", false)
                    })
                    .LifeStyle.Singleton
                    .Named("NGEngine"));
            }

            if (_wc.GetService<ITaskDatabaseSessionFactory>() == null)
            {
                _wc.Register(Component.For<ITaskDatabaseSessionFactory>()
                    .ImplementedBy<NGinnBPM.Dao.SqlTaskDbSessionFactory>()
                    .DependsOn(new {
                        ConnectionString = NGinnDBConnectionString,
                        UseMessageBusConnection = true
                    })
                    .LifeStyle.Singleton);
            }

            if (_wc.GetService<ITaskInstanceRepository>() == null)
            {
                _wc.Register(Component.For<ITaskInstanceRepository>()
                    .ImplementedBy<NGinnBPM.Dao.RawSqlSerializedTaskInstanceRepository>()
                    .DependsOn(new {
                        TableName = _cfg.GetString("NGinnBPM.SqlTaskRepository.TableName", "TaskInstanceData"),
                        AutoCreateDatabase = true,
                        ProcessStateRetentionPeriod = TimeSpan.FromDays(_cfg.GetDouble("NGinnBPM.SqlTaskRepository.KeepProcessStateForDays", 30))
                    }).LifeStyle.Singleton);
            }

            if (_wc.GetService<IResourceLockManager>() == null)
            {
                _wc.Register(Component.For<IResourceLockManager>()
                    .ImplementedBy<ResourceLockManager>()
                    .LifeStyle.Singleton);
            }

            _wc.Register(Component.For<IHttpClient>()
                .ImplementedBy<NGinnBPM.Services.Utilities.HttpClient>());

            
            if (!string.IsNullOrEmpty(_cfg.GetString("NGinnBPM.WcfWebServicesUrl", null)))
            {
                log.Info("Configuring WCF web services at {0}", _cfg.GetString("NGinnBPM.WcfWebServicesUrl", null));
                var host = NGinnBPM.Services.Wcf.WcfNGinnServicesHost.ConfigureBasicNGinnServices(_cfg.GetString("NGinnBPM.WcfWebServicesUrl", null));
                var sd = new NGinnBPM.Services.Util.StartableDelegate(delegate() {
                    log.Info("Starting WCF web service host");
                    host.Open(); 
                }, delegate() {
                    log.Info("Stopping WCF web service host");
                    host.Close(); 
                });
                _wc.Register(Component.For<IStartableService>().Instance(sd));
            }
            RegisterNGinnHttpServices();
            

            _host = new NGinnHost(_wc.Resolve<IServiceResolver>());
            
            //ServiceStack.Logging.LogManager.LogFactory = new ServiceStack.Logging.Support.Logging.ConsoleLogFactory();
            return this;
        }

        
        

        
        public static void Test()
        {
        }

        /// <summary>
        /// Base directory for NGinn Host.
        /// All file locations are specified relative to this dir.
        /// Warning: after loading a configuration file this directory
        /// will be set to the directory where config file is stored.
        /// </summary>
        public string BaseDirectory
        {
            get { return _cfg.GetString("configdir", AppDomain.CurrentDomain.BaseDirectory); }
            set { _cfg.Set("configdir", value); }
        }

        public string NGinnDBConnectionString
        {
            get 
            { 
                var s = _cfg.GetString("NGinnBPM.TaskDatabase.ConnectionString");
                if (!string.IsNullOrEmpty(s))
                {
                    var cs = System.Configuration.ConfigurationManager.ConnectionStrings[s];
                    if (cs != null) return cs.ConnectionString;
                }
                return s;
            }
            set { _cfg.Set("NGinnBPM.TaskDatabase.ConnectionString", value); }
        }

        public string PluginDirectory
        {
            get { return _cfg.GetString("plugindir"); }
            set { _cfg.Set("plugindir", value); }
        }


        public string NGinnMessageBusEndpoint
        {
            get { return _cfg.GetString("NGinnBPM.MessageBus.Endpoint"); }
            set { _cfg.Set("NGinnBPM.MessageBus.Endpoint", value); }
        }

        private static readonly string NGINNMESSAGEBUS_CONNSTR_PREFIX = "NGinnBPM.MessageBus.ConnectionString";

        /// <summary>
        /// Get a dictionary containing NGinn message bus connection strings.
        /// Warn: Use AddMessageBusConnectionString to add/modify NMBus conn strings.
        /// </summary>
        /// <returns></returns>
        public IDictionary<string, string> GetNGinnMessageBusConnectionStrings()
        {
            Dictionary<string, string> d = new Dictionary<string, string>();

            foreach (string prm in _cfg.GetParameterNames())
            {
                if (prm.StartsWith(NGINNMESSAGEBUS_CONNSTR_PREFIX))
                {
                    string prefix = prm.Substring(NGINNMESSAGEBUS_CONNSTR_PREFIX.Length);
                    if (prefix.Length > 0)
                        prefix = prefix.Substring(1);
                    d[prefix] = _cfg.GetString(prm);
                }
            }
            return d;
        }

        public NGinnHostConfigurator RegisterNGinnHostForRemoting(string url)
        {
            System.Runtime.Remoting.ObjRef oref = System.Runtime.Remoting.RemotingServices.Marshal(_host, url, typeof(INGinnHost));
            log.Info("Registered nginn host for remoting: {0}", oref.URI);
            return this;
        }

        /// <summary>
        /// Expose NGinn (INGinn interface) as a WCF self-hosted service.
        /// </summary>
        /// <returns></returns>
        public NGinnHostConfigurator EnableWcfWebServices(string baseUrl)
        {
            _cfg.Set("NGinnBPM.WcfWebServicesUrl", baseUrl);
            return this;    
        }

        
        /// <summary>
        /// Return NGinn host object
        /// </summary>
        /// <returns></returns>
        public INGinnHost GetNGinnHost()
        {
            return _host;
        }

    }
}
